# -*- coding: utf-8 -*-

'''
PyMuPDF页面的包装器，作为页面引擎。
'''

import fitz
import logging
from .RawPage import RawPage
from ..image.ImagesExtractor import ImagesExtractor
from ..shape.Paths import Paths
from ..common.constants import FACTOR_A_HALF
from ..common.Element import Element
from ..common.share import (RectType, debug_plot)
from ..common.algorithm import get_area


class RawPageFitz(RawPage):
    '''``fitz.Page``的包装器，用于提取源内容。'''

    def extract_raw_dict(self, **settings):
        raw_dict = {}
        if not self.page_engine: return raw_dict

        # 实际页面大小
        # `self.page_engine` 是 `fitz.Page`。
        *_, w, h = self.page_engine.rect # 始终反映页面旋转
        raw_dict.update({ 'width' : w, 'height': h })
        self.width, self.height = w, h

        # 预处理布局元素。例如文本、图像和形状
        text_blocks = self._preprocess_text(**settings)
        raw_dict['blocks'] = text_blocks

        image_blocks = self._preprocess_images(**settings)
        raw_dict['blocks'].extend(image_blocks)
        
        shapes, images =  self._preprocess_shapes(**settings)
        raw_dict['shapes'] = shapes
        raw_dict['blocks'].extend(images)

        hyperlinks = self._preprocess_hyperlinks()
        raw_dict['shapes'].extend(hyperlinks)        
       
        # Element是处理坐标的基类，所以全局设置旋转矩阵
        Element.set_rotation_matrix(self.page_engine.rotation_matrix)

        return raw_dict
    

    def _preprocess_text(self, **settings):
        '''提取页面文本并识别隐藏文本。
        
        注意：所有坐标都是相对于未旋转的页面。

            https://pymupdf.readthedocs.io/en/latest/page.html#modifying-pages
            https://pymupdf.readthedocs.io/en/latest/functions.html#Page.get_texttrace
            https://pymupdf.readthedocs.io/en/latest/textpage.html
        '''
        ocr = settings['ocr']
        if ocr==1: raise SystemExit("OCR功能已计划但尚未实现。")

        # 所有文本块，无论是否隐藏
        sort = settings.get('sort')
        raw = self.page_engine.get_text(
                'rawdict',
                flags=0
                    | fitz.TEXT_MEDIABOX_CLIP
                    | fitz.TEXT_CID_FOR_UNKNOWN_UNICODE
                    ,
                sort=sort,
                )
        text_blocks = raw.get('blocks', [])

        # 尝试过滤隐藏文本时可能出现UnicodeDecodeError问题：
        # https://github.com/dothinking/pdf2docx/issues/144
        # https://github.com/dothinking/pdf2docx/issues/155
        try:
            spans = self.page_engine.get_texttrace()
        except SystemError:
            logging.warning('由于上游库中的UnicodeDecodeError，忽略隐藏文本检查。')
            spans = []
        
        if not spans: return text_blocks

        # 如果ocr=0则忽略隐藏文本，如果ocr=2则仅提取隐藏文本
        if ocr==2:
            f = lambda span: span['type']!=3  # 查找显示的文本并忽略它
        else:
            f = lambda span: span['type']==3  # 查找隐藏文本并忽略它
        filtered_spans = list(filter(f, spans))
        
        def span_area(bbox):
            x0, y0, x1, y1 = bbox
            return (x1-x0) * (y1-y0)

        # 通过检查span交叉来过滤块：如果
        # 任何span匹配则标记整个块
        blocks = []
        for block in text_blocks:
            intersected = False
            for line in block['lines']:
                for span in line['spans']:
                    for filter_span in filtered_spans:
                        intersected_area = get_area(span['bbox'], filter_span['bbox'])
                        if intersected_area / span_area(span['bbox']) >= FACTOR_A_HALF \
                            and span['font']==filter_span['font']:
                            intersected = True
                            break
                    if intersected: break # 如果找到则跳过进一步的span检查
                if intersected: break     # 如果找到则跳过进一步的行检查

            # 如果没有与过滤span的任何交集，则保留块
            if not intersected: blocks.append(block)

        return blocks


    def _preprocess_images(self, **settings):
        '''提取图像块。通过``page.get_text('rawdict')``提取的图像块不
        包含alpha通道数据，所以必须通过``page.get_images()``获取页面图像，然后
        恢复它们。注意，``Page.get_images()``每个图像只包含一次，即
        忽略重复出现。
        '''
        # 如果是ocr处理的pdf则忽略图像：仅获取ocr处理的文本
        if settings['ocr']==2: return []
        
        return ImagesExtractor(self.page_engine).extract_images(settings['clip_image_res_ratio'])


    def _preprocess_shapes(self, **settings):
        '''识别等向路径并将矢量图形路径转换为位图。'''
        paths = self._init_paths(**settings)
        return paths.to_shapes_and_images(
            settings['min_svg_gap_dx'], 
            settings['min_svg_gap_dy'], 
            settings['min_svg_w'], 
            settings['min_svg_h'], 
            settings['clip_image_res_ratio'])
    

    @debug_plot('源路径')
    def _init_paths(self, **settings):
        '''基于用PyMuPDF提取的绘图初始化路径。'''
        raw_paths = self.page_engine.get_cdrawings()
        return Paths(parent=self).restore(raw_paths)
    

    def _preprocess_hyperlinks(self):
        """获取源超链接字典。

        返回:
            list: 源超链接字典列表。
        """
        hyperlinks = []
        for link in self.page_engine.get_links():
            if link['kind']!=2: continue # 仅考虑互联网地址
            hyperlinks.append({
                'type': RectType.HYPERLINK.value,
                'bbox': tuple(link['from']),
                'uri' : link['uri']
            })

        return hyperlinks
